widget: Add surface relative transform changed callback
authorJonas Ådahl <jadahl@gmail.com>
Thu, 18 Apr 2019 16:55:13 +0000 (18:55 +0200)
committerJonas Ådahl <jadahl@gmail.com>
Wed, 24 Apr 2019 12:26:24 +0000 (14:26 +0200)
Added two new private GtkWidget API:

 * gtk_widget_add_surface_transform_changed_callback()
 * gtk_widget_remove_surface_transform_changed_callback()

The intention is to let the user know when a widget transform relative
to the surface changes. It works by calculating the surface relative
transform during allocation, and notifying the callbacks if it changed
since last time. Each widget adds itself as a listener to its parent
widget, thus will be triggered if a parents surface relative transform
changes.

gtk/gtkwidget.c
gtk/gtkwidgetprivate.h
meson.build

index ed93a7ba20a8c54e69ee8dc912a03b07e299f5ed..ab33790cb21333982fb9cdf19b9bdd573576cce6 100644 (file)
@@ -713,6 +713,9 @@ static void gtk_widget_update_input_shape (GtkWidget *widget);
 
 static gboolean gtk_widget_class_get_visible_by_default (GtkWidgetClass *widget_class);
 
+static void remove_parent_surface_transform_changed_listener (GtkWidget *widget);
+static void add_parent_surface_transform_changed_listener (GtkWidget *widget);
+
 
 /* --- variables --- */
 static gint             GtkWidget_private_offset = 0;
@@ -2905,6 +2908,9 @@ gtk_widget_root (GtkWidget *widget)
   if (priv->context)
     gtk_style_context_set_display (priv->context, gtk_root_get_display (priv->root));
 
+  if (priv->surface_transform_changed_callbacks)
+    add_parent_surface_transform_changed_listener (widget);
+
   GTK_WIDGET_GET_CLASS (widget)->root (widget);
 
   g_object_notify_by_pspec (G_OBJECT (widget), widget_props[PROP_ROOT]);
@@ -2922,6 +2928,9 @@ gtk_widget_unroot (GtkWidget *widget)
   g_assert (priv->root);
   g_assert (!priv->realized);
 
+  if (priv->parent_surface_transform_changed_parent)
+    remove_parent_surface_transform_changed_listener (widget);
+
   GTK_WIDGET_GET_CLASS (widget)->unroot (widget);
 
   if (priv->context)
@@ -3586,6 +3595,249 @@ gtk_widget_disconnect_frame_clock (GtkWidget *widget)
     }
 }
 
+typedef struct _GtkSurfaceTransformChangedCallbackInfo GtkSurfaceTransformChangedCallbackInfo;
+
+struct _GtkSurfaceTransformChangedCallbackInfo
+{
+  guint id;
+  GtkSurfaceTransformChangedCallback callback;
+  gpointer user_data;
+  GDestroyNotify notify;
+};
+
+static void
+surface_transform_changed_callback_info_destroy (GtkSurfaceTransformChangedCallbackInfo *info)
+{
+  if (info->notify)
+    info->notify (info->user_data);
+
+  g_slice_free (GtkSurfaceTransformChangedCallbackInfo, info);
+}
+
+static void
+notify_surface_transform_changed (GtkWidget *widget)
+{
+  GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
+  graphene_matrix_t *surface_transform;
+  GList *l;
+
+  if (priv->cached_surface_transform_valid)
+    surface_transform = &priv->cached_surface_transform;
+  else
+    surface_transform = NULL;
+
+  for (l = priv->surface_transform_changed_callbacks; l;)
+    {
+      GtkSurfaceTransformChangedCallbackInfo *info = l->data;
+      GList *l_next = l->next;
+
+      if (info->callback (widget,
+                          surface_transform,
+                          info->user_data) == G_SOURCE_REMOVE)
+        {
+          priv->surface_transform_changed_callbacks =
+            g_list_delete_link (priv->surface_transform_changed_callbacks, l);
+          surface_transform_changed_callback_info_destroy (info);
+        }
+
+      l = l_next;
+    }
+}
+
+static void
+destroy_surface_transform_changed_callbacks (GtkWidget *widget)
+{
+  GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
+  GList *l;
+
+  for (l = priv->surface_transform_changed_callbacks; l;)
+    {
+      GtkSurfaceTransformChangedCallbackInfo *info = l->data;
+      GList *l_next = l->next;
+
+      priv->surface_transform_changed_callbacks =
+        g_list_delete_link (priv->surface_transform_changed_callbacks, l);
+      surface_transform_changed_callback_info_destroy (info);
+
+      l = l_next;
+    }
+}
+
+static void
+sync_widget_surface_transform (GtkWidget *widget)
+{
+  GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
+  gboolean was_valid;
+  graphene_matrix_t prev_transform;
+
+  was_valid = priv->cached_surface_transform_valid;
+  prev_transform = priv->cached_surface_transform;
+
+  if (GTK_IS_ROOT (widget))
+    {
+      gsk_transform_to_matrix (priv->transform,
+                               &priv->cached_surface_transform);
+      priv->cached_surface_transform_valid = TRUE;
+    }
+  else if (!priv->root)
+    {
+      priv->cached_surface_transform_valid = FALSE;
+    }
+  else if (gtk_widget_compute_transform (widget, GTK_WIDGET (priv->root),
+                                         &priv->cached_surface_transform))
+    {
+      priv->cached_surface_transform_valid = TRUE;
+    }
+  else
+    {
+      g_warning ("Could not compute surface transform");
+      priv->cached_surface_transform_valid = FALSE;
+    }
+
+  if (was_valid != priv->cached_surface_transform_valid ||
+      (was_valid && priv->cached_surface_transform_valid &&
+       !graphene_matrix_equal (&priv->cached_surface_transform,
+                               &prev_transform)))
+    notify_surface_transform_changed (widget);
+}
+
+static guint surface_transform_changed_callback_id;
+
+static gboolean
+parent_surface_transform_changed_cb (GtkWidget               *parent,
+                                     const graphene_matrix_t *transform,
+                                     gpointer                 user_data)
+{
+  GtkWidget *widget = GTK_WIDGET (user_data);
+
+  sync_widget_surface_transform (widget);
+
+  return G_SOURCE_CONTINUE;
+}
+
+static void
+remove_parent_surface_transform_changed_listener (GtkWidget *widget)
+{
+  GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
+
+  g_assert (priv->parent_surface_transform_changed_parent);
+
+  gtk_widget_remove_surface_transform_changed_callback (
+    priv->parent_surface_transform_changed_parent,
+    priv->parent_surface_transform_changed_id);
+  priv->parent_surface_transform_changed_id = 0;
+  g_clear_object (&priv->parent_surface_transform_changed_parent);
+}
+
+static void
+add_parent_surface_transform_changed_listener (GtkWidget *widget)
+{
+  GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
+  GtkWidget *parent;
+
+  g_assert (!priv->parent_surface_transform_changed_parent);
+
+  parent = priv->parent;
+  priv->parent_surface_transform_changed_id =
+    gtk_widget_add_surface_transform_changed_callback (
+      parent,
+      parent_surface_transform_changed_cb,
+      widget,
+      NULL);
+  priv->parent_surface_transform_changed_parent = g_object_ref (parent);
+}
+
+/**
+ * gtk_widget_add_surface_transform_changed_callback:
+ * @widget: a #GtkWidget
+ * @callback: a function to call when the surface transform changes
+ * @user_data: data to pass to @callback
+ * @notify: function to call to free @user_data when the callback is removed
+ *
+ * Invokes the callback whenever the surface relative transform of the widget
+ * changes.
+ *
+ * Returns: an id for the connection of this callback. Remove the callback by
+ *     passing the id returned from this funcction to
+ *     gtk_widget_remove_surface_transform_changed_callback()
+ */
+guint
+gtk_widget_add_surface_transform_changed_callback (GtkWidget                          *widget,
+                                                   GtkSurfaceTransformChangedCallback  callback,
+                                                   gpointer                            user_data,
+                                                   GDestroyNotify                      notify)
+{
+  GtkWidgetPrivate *priv;
+  GtkSurfaceTransformChangedCallbackInfo *info;
+
+  g_return_val_if_fail (GTK_IS_WIDGET (widget), 0);
+  g_return_val_if_fail (callback, 0);
+
+  priv = gtk_widget_get_instance_private (widget);
+
+  if (priv->parent && !priv->parent_surface_transform_changed_id)
+    add_parent_surface_transform_changed_listener (widget);
+
+  if (!priv->surface_transform_changed_callbacks)
+    sync_widget_surface_transform (widget);
+
+  info = g_slice_new0 (GtkSurfaceTransformChangedCallbackInfo);
+
+  info->id = ++surface_transform_changed_callback_id;
+  info->callback = callback;
+  info->user_data = user_data;
+  info->notify = notify;
+
+  priv->surface_transform_changed_callbacks =
+    g_list_prepend (priv->surface_transform_changed_callbacks, info);
+
+  return info->id;
+}
+
+/**
+ * gtk_widget_remove_surface_transform_changed_callback:
+ * @widget: a #GtkWidget
+ * @id: an id returned by gtk_widget_add_surface_transform_changed_callback()
+ *
+ * Removes a surface transform changed callback previously registered with
+ * gtk_widget_add_surface_transform_changed_callback().
+ */
+void
+gtk_widget_remove_surface_transform_changed_callback (GtkWidget *widget,
+                                                      guint      id)
+{
+  GtkWidgetPrivate *priv;
+  GList *l;
+
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+  g_return_if_fail (id);
+
+  priv = gtk_widget_get_instance_private (widget);
+
+  for (l = priv->surface_transform_changed_callbacks; l; l = l->next)
+    {
+      GtkSurfaceTransformChangedCallbackInfo *info = l->data;
+
+      if (info->id == id)
+        {
+          priv->surface_transform_changed_callbacks =
+            g_list_delete_link (priv->surface_transform_changed_callbacks, l);
+
+          surface_transform_changed_callback_info_destroy (info);
+          break;
+        }
+    }
+
+  if (!priv->surface_transform_changed_callbacks)
+    {
+      if (priv->parent_surface_transform_changed_parent)
+        remove_parent_surface_transform_changed_listener (widget);
+
+      g_signal_handler_disconnect (widget, priv->parent_changed_handler_id);
+      priv->parent_changed_handler_id = 0;
+    }
+}
+
 /**
  * gtk_widget_realize:
  * @widget: a #GtkWidget
@@ -4200,6 +4452,9 @@ gtk_widget_allocate (GtkWidget    *widget,
 
   priv->transform = transform;
 
+  if (priv->surface_transform_changed_callbacks)
+    sync_widget_surface_transform (widget);
+
   if (!alloc_needed && !size_changed && !baseline_changed)
     {
       /* Still have to move the window... */
@@ -8125,6 +8380,7 @@ gtk_widget_real_destroy (GtkWidget *object)
   gtk_grab_remove (widget);
 
   destroy_tick_callbacks (widget);
+  destroy_surface_transform_changed_callbacks (widget);
 }
 
 static void
index 7409ee047b6b7b5ac84b7cb42d2e196624b2b156..2e596f24c4740e3a280432fc70446dcd8ef8fcf4 100644 (file)
 
 G_BEGIN_DECLS
 
+typedef gboolean (*GtkSurfaceTransformChangedCallback) (GtkWidget               *widget,
+                                                        const graphene_matrix_t *surface_transform,
+                                                        gpointer                 user_data);
+
 #define GTK_STATE_FLAGS_BITS 14
 
 struct _GtkWidgetPrivate
@@ -116,6 +120,14 @@ struct _GtkWidgetPrivate
   guint clock_tick_id;
   GList *tick_callbacks;
 
+  /* Surface relative transform updates callbacks */
+  guint parent_surface_transform_changed_id;
+  GtkWidget *parent_surface_transform_changed_parent;
+  gulong parent_changed_handler_id;
+  GList *surface_transform_changed_callbacks;
+  gboolean cached_surface_transform_valid;
+  graphene_matrix_t cached_surface_transform;
+
   /* The widget's name. If the widget does not have a name
    * (the name is NULL), then its name (as returned by
    * "gtk_widget_get_name") is its class's name.
@@ -344,6 +356,15 @@ gboolean          gtk_widget_run_controllers               (GtkWidget
                                                             GtkPropagationPhase  phase);
 
 
+guint             gtk_widget_add_surface_transform_changed_callback (GtkWidget                          *widget,
+                                                                     GtkSurfaceTransformChangedCallback  callback,
+                                                                     gpointer                            user_data,
+                                                                     GDestroyNotify                      notify);
+
+void              gtk_widget_remove_surface_transform_changed_callback (GtkWidget *widget,
+                                                                        guint      id);
+
+
 /* inline getters */
 
 static inline GtkWidget *
index 2060fea5a1af94874878f3b7d7aa29b2b3ba76fa..6e2b8eacd1c367f68d8941acc8c6312b26c5ea71 100644 (file)
@@ -35,7 +35,7 @@ gdk_pixbuf_req     = '>= 2.30.0'
 introspection_req  = '>= 1.39.0'
 wayland_proto_req  = '>= 1.12'
 wayland_req        = '>= 1.14.91'
-graphene_req       = '>= 1.8.5'
+graphene_req       = '>= 1.8.7'
 epoxy_req          = '>= 1.4'
 cloudproviders_req = '>= 0.2.5'
 xkbcommon_req      = '>= 0.2.0'